package thread;

/**
 * 同步块
 * 语法:
 * synchronized(同步监视器对象){
 *     需要多个线程同步执行的代码片段(多线程会产生并发安全问题的代码)
 * }
 *
 * 有效的缩小同步范围是可以在保证并发安全的前提下尽可能提高并发效率
 */
public class SyncDemo2 {
    public static void main(String[] args) {
        Shop shop = new Shop();
        Shop shop1 = new Shop();
        Shop shop2 = new Shop();
        Thread t1 = new Thread("小王"){
            public void run(){
                shop.buy();
            }
        };
        Thread t2 = new Thread("老六"){
            public void run(){
                shop.buy();
            }
        };
        Thread t3 = new Thread("小丽"){
            public void run(){
                shop.buy();
            }
        };
        t1.start();
        t2.start();
        t3.start();
    }
}

class Shop{
    /*
        在方法上使用synchronized时,同步监视器对象不可选,只能是this.
     */
    //public synchronized void buy(){
    public void buy(){
        try {
            Thread t = Thread.currentThread();//获取1运行buy方法的线程
            System.out.println(t.getName()+":正在挑衣服...");
            Thread.sleep(5000);
            /*
                同步块可以更精准的控制需要同步执行的代码片段
                但是使用同步块时要指定同步监视器对象,同步监视器对象要同时具备以下条件:
                1:必须是引用类型实例
                2:多个需要同步执行该代码片段的线程看到的必须是同一个对象

                合适的锁对象,应当是在多个线程出现"抢"的时候发挥左右,否则不发发挥作用
                例如:两个线程分别执行两个Shop实例的buy方法(相当于两个人去不同的商店)时,那么这两个
                线程就不发生"抢"的现象,此时下面的同步块就不应当要求线程排队执行代码.
                如果同步监视器对象选取了字符串字面量(字符串字面量在任何情况下都是同一个对象),那么在
                这样的情况下也要求多个线程排队执行显然是不合适的.
             */
            /*
                synchronized不是公平锁,当一个线程进入后,如果后续有5个线程陆续执行到这里开始排队时
                当进入的线程出了同步块后,并不是后五个中先排队的线程先进入,而是后五个线程谁先拿到时间片
                谁先进入执行.
                如果想实现公平锁,可以使用JUC(java.util.concurrent包),java并发包
                这个包中包含了很多和并发相关的API.

                java.util.concurrent.locks.ReentrantLock 可重入锁可以实现公平锁机制
             */
            synchronized (this){
            //synchronized (new Object()){//无效的锁对象，因为多个线程看到的不是同一个对象
            //synchronized ("hello"){//有效但不合适，常量池不变
                System.out.println(t.getName()+":正在试衣服...");
                Thread.sleep(5000);
            }
            System.out.println(t.getName()+":结账离开");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}